﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

// TODO: Review the design of this class.

namespace QUT.Bio.Graphs {
	/// <summary>
	/// A NodeCollection maps node id values to their associated application-specific node object.
	/// <para>Nodes may be added to and removed from the collection, and additionally the contents of the collection may be replaced.</para>
	/// <para>Dependant objects may subscribe to the CollectionChanged event to receive notifications when objects are added or removed.</para>
	/// </summary>
	/// <typeparam name="NodeType">The Id type of nodes in the collection.</typeparam>

	public class NodeCollection<NodeType> : IEnumerable<Node<NodeType>>
		where NodeType : IComparable<NodeType>, IEquatable<NodeType> {

		#region Fields
		private readonly Dictionary<NodeType, Node<NodeType>> nodes = 
			new Dictionary<NodeType, Node<NodeType>>();
		#endregion

		#region Events
		/// <summary>
		/// The CollectionChanged event is fired when nodes are added or removed.
		/// </summary>

		public event Action<NodeCollection<NodeType>> CollectionChanged;
		#endregion

		#region Properties
		/// <summary>
		/// Retrieve a reference to t he node having a specific key.
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		/// <exception cref="System.ArgumentNullException">key is null.</exception>
		/// <exception cref="System.Collections.Generic.KeyNotFoundException">The key does not exist in this collection.</exception>

		public Node<NodeType> this[NodeType index] {
			get {
				return nodes[index];
			}
		}
		
		/// <summary>
		/// Gets the number of nodes in the collection.
		/// </summary>

		public int Count {
			get {
				return this.nodes.Count;
			}
		}
		#endregion

		#region Methods
		/// <summary>
		/// Adds a node to the collection and triggers the CollectionChanged event.
		/// </summary>
		/// <param name="newNode">A node to be added.</param>
		/// <exception cref="System.ArgumentNullException">newNode.Id is null</exception>
		/// <exception cref="System.ArgumentException">A node with the same Id is already present in the collection.</exception>

		public void Add ( Node<NodeType> newNode ) {
			nodes.Add( newNode.Content, newNode );
			SignalCollectionChanged();
		}

		/// <summary>
		/// Adds a sequence of nodes to the collection, then triggers the CollectionChanged event.
		/// </summary>
		/// <param name="newNodes"></param>
		/// <exception cref="System.ArgumentNullException">newEdges is null or one of the node ids is null</exception>
		/// <exception cref="System.ArgumentException">The sequence of nodes to be added contains a node with the same Id as one already present in the collection.</exception>

		public void AddRange ( IEnumerable<Node<NodeType>> newNodes ) {
			bool changed = false;

			foreach ( var node in newNodes ) {
				nodes.Add( node.Content, node );
				changed = true;
			}

			if ( changed ) {
				SignalCollectionChanged();
			}
		}

		/// <summary>
		/// If this collection is not empty, removes all nodes from this collection and triggers the CollectionChanged event.
		/// </summary>

		public void Clear () {
			if ( nodes.Count > 0 ) {
				nodes.Clear();
				SignalCollectionChanged();
			}
		}

		/// <summary>
		/// Determines the presence or otherwise of a node having the specified id.
		/// </summary>
		/// <param name="index">The node id to query.</param>
		/// <returns>True iff this collection contains a node with the specified id.</returns>

		public bool ContainsKey ( NodeType index ) {
			return nodes.ContainsKey( index );
		}

		/// <summary>
		/// Gets an enumerator that traverses the nodes in this collection
		/// </summary>
		/// <returns>An enumerator that traverses the nodes in this collection</returns>

		public IEnumerator<Node<NodeType>> GetEnumerator () {
			return nodes.Values.GetEnumerator();
		}

		/// <summary>
		/// Gets an enumerator that traverses the nodes in this collection
		/// </summary>
		/// <returns>An enumerator that traverses the nodes in this collection</returns>

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
			return nodes.Values.GetEnumerator();
		}

		/// <summary>
		/// Removes a node from the collection.
		/// </summary>
		/// <param name="node">The node to be removed.</param>
		/// <exception cref="System.ArgumentNullException">The node is null or its Id is null.</exception>

		public void Remove ( Node<NodeType> node ) {
			nodes.Remove( node.Content );
			SignalCollectionChanged();
		}

		/// <summary>
		/// Removes all nodes that match a supplied predicate from the collection.
		/// </summary>
		/// <param name="where">A predicate used to filter out nodes.</param>
		/// <exception cref="System.ArgumentNullException">The node is null or its Id is null.</exception>

		public void RemoveAll ( Predicate<Node<NodeType>> where ) {
			Node<NodeType>[] existingNodes = nodes.Values.ToArray();
			bool changed = false;

			foreach ( var node in existingNodes ) {
				if ( where( node ) ) {
					nodes.Remove( node.Content );
					changed = true;
				}
			}

			if ( changed ) {
				SignalCollectionChanged();
			}
		}

		/// <summary>
		/// Replaces the contents of the collection with a sequence of nodes to the collection, then triggers the CollectionChanged event.
		/// <para>This is basically the same as Clear() followed by AddRange, except we only fire CollectionChanged once.</para>
		/// </summary>
		/// <param name="newNodes"></param>
		/// <exception cref="System.ArgumentNullException">newEdges is null or one of the node ids is null</exception>
		/// <exception cref="System.ArgumentException">The sequence of nodes to be added contains a node with the same Id as one already present in the collection.</exception>

		public void ReplaceAll ( IEnumerable<Node<NodeType>> newNodes ) {
			bool changed = false;

			if ( nodes.Count > 0 ) {
				nodes.Clear();
				changed = true;
			}

			foreach ( var node in newNodes ) {
				nodes.Add( node.Content, node );
				changed = true;
			}

			if ( changed ) {
				SignalCollectionChanged();
			}
		}

		/// <summary>
		/// Removes all items matching the supplied predicate, then adds all items in a sequence of nodes, and finally (if something changed) triggers the Collectionchanged event.
		/// <para>This is basically the same as RemoveAll( where ) followed by AddRange, except we only fire CollectionChanged once.</para>
		/// </summary>
		/// <param name="newNodes">A sequence of nodes to be added to the collection.</param>
		/// <param name="where">A predicate used to filter out unwnated nodes.</param>
		/// <exception cref="System.ArgumentNullException">newEdges is null or one of the node ids is null</exception>
		/// <exception cref="System.ArgumentException">The sequence of nodes to be added contains a node with the same Id as one already present in the collection.</exception>

		public void ReplaceAll (
			IEnumerable<Node<NodeType>> newNodes,
			Predicate<Node<NodeType>> where
		) {
			Node<NodeType>[] existingNodes = nodes.Values.ToArray();
			bool changed = false;

			foreach ( var node in existingNodes ) {
				if ( where( node ) ) {
					nodes.Remove( node.Content );
					changed = true;
				}
			}

			foreach ( var node in newNodes ) {
				nodes.Add( node.Content, node );
				changed = true;
			}

			if ( changed ) {
				SignalCollectionChanged();
			}
		}

		/// <summary>
		/// Fires the CollectionChanged event.
		/// </summary>

		private void SignalCollectionChanged () {
			if ( CollectionChanged != null ) {
				CollectionChanged( this );
			}
		}
		#endregion
	}
}
